FreeRTOS 信号量和互斥量

信号量(Semaphore)也是一种任务间通信的常用方式,通常用于任务的同步。

1#include <semphr.h>
2 
3BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
4//产生一个信号,成功返回pdPASS
5//信号量已满则失败,返回errQUEUE_FULL
6
7BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime);
8//获取一个信号,信号量为空则阻塞
9//第二个参数是阻塞超时时间,成功返回pdTRUE,超时返回pdFALSE

值信号量是只有两个值(例如0或1、满或空)的信号量,FreeRTOS的二值信号量相当于只有一个元素的队列,用满和空表示信号量的值。

1#include <semphr.h>
2 
3SemaphoreHandle_t xSemaphoreCreateBinary(void);
4//创建一个二值信号量,成功返回信号量句柄,失败返回NULL
  • 二值信号量为空时调用xSemaphoreGive,信号量变满;为满时失败。
  • 二值信号量为满时调用xSemaphoreTake,信号量变空;为空时阻塞。

计数信号量顾名思义是计数用的信号量。

1#include <semphr.h>
2 
3SemaphoreHandle_t xSemaphoreCreateCounting(void);
4//创建一个计数信号量,成功返回信号量句柄,失败返回NULL
5 
6UBaseType_t uxSemaphoreGetCount(const QueueHandle_t xSemaphore);
7//返回计数信号量的计数值
  • xSemaphoreGive使计数信号量加一。
  • xSemaphoreTake使计数信号量减一。

信号量通常用于任务同步,例如多个任务共享资源时,使用二值信号量进行访问控制,二值信号量为满则表示资源可用,二值信号量为空则表示资源正在被使用。也可以使用计数信号量计数有多少个任务在使用共享资源。

下面这个例子使用二值信号量标志串口是否被使用:

1#include <stm32f4xx.h>
2#include <FreeRTOS.h>
3#include <task.h>
4#include <queue.h>
5#include <semphr.h>
6#include <uart.h>
7 
8void task1(void* args);
9void task2(void* args);
10 
11SemaphoreHandle_t binary;
12 
13int main()
14{
15    //配置USART1
16    USART1_Config();
17    //创建任务
18    TaskHandle_t h1,h2;
19    //创建二值信号量,用这个信号量标识串口是否可用
20    binary = xSemaphoreCreateBinary();
21    //使二值信号量变为满,表示资源可用
22    xSemaphoreGive(binary);
23    xTaskCreate(task1,"task1",configMINIMAL_STACK_SIZE,NULL,1,&h1);
24    xTaskCreate(task2,"task2",configMINIMAL_STACK_SIZE,NULL,1,&h2);
25    //开启任务调度
26    vTaskStartScheduler();
27    while(1);
28}
29 
30 
31void task1(void* args)
32{
33    int i = 0;
34    while(1)
35    {
36        //循环五次后删除自己
37        if(i >= 5)
38        {
39            vTaskDelete(NULL);
40        }
41        //使二值信号量变为空,表示资源被占用
42        xSemaphoreTake(binary,100/portTICK_RATE_MS);
43        USART_printf(USART1,"The task1 is running now.\n");
44        //使二值信号量变为满,表示资源可用
45        xSemaphoreGive(binary);
46        i++;
47    }
48}
49 
50void task2(void* args)
51{
52    int i = 0;
53    while(1)
54    {
55        //循环十次后删除自己
56        if(i >= 10)
57        {
58            vTaskDelete(NULL);
59        }
60        //使二值信号量变为空,表示资源被占用
61        xSemaphoreTake(binary,100/portTICK_RATE_MS);
62        USART_printf(USART1,"The task2 is running now.\n");
63        //使二值信号量变为满,表示资源可用
64        xSemaphoreGive(binary);
65        i++;
66    }
67}

Image

不过这种方式可能导致优先级反转:

1(1)假设优先级task1>task2>task3  
2(2)task1和task2阻塞或挂起  
3(3)task3运行并通过二值信号量占用了某个公共资源  
4(4)task1就绪并试图通过二值信号量使用被task3占用的资源,但由于资源被占用,task1阻塞  
5(5)task2就绪,由于它优先级高于task3,因此task2优先执行  
6(6)task2执行完之后task3继续执行  
7(7)task3释放资源,task1解除阻塞并执行  

如此以来优先级相当于变成了task2>task1,原本优先级最高的task1不能及时运行,这在实时性要求高的场合是不被允许的。

互斥量(Mutex)是处理共享资源同步的常用方式。在FreeRTOS的实现中,互斥量是一种特殊的二值信号量,其操作方式和二值信号量一样,但互斥量具有优先级继承的特性。

1(1)假设优先级task1>task2>task3  
2(2)task1和task2阻塞或挂起  
3(3)task3运行并通过互斥量占用了某个公共资源  
4(4)task1就绪并试图通过互斥量使用被task3占用的资源,但由于资源被占用,task1阻塞  
5(5)task3的优先级被提升到和task1一样  

如此一来就不会发生task2在task1之前运行的情况,提高了实时性。

xSemaphoreCreateMutex用于创建互斥量。

1#include <semphr.h>
2 
3SemaphoreHandle_t xSemaphoreCreateMutex(void);
4//创建一个互斥量,成功返回信号量句柄,失败返回NULL

假如任务中调用一个函数来使用公共资源,在调用这个函数前使用xSemaphoreTake占用该资源的互斥量,在这个函数返回后使用xSemaphoreGive来释放该资源的互斥量。但这个函数中可能也使用了xSemaphoreTakexSemaphoreGive来占用和释放资源。这种情况下就会导致错误。

递归互斥量可以让同一个任务多次占用资源,在释放同等次数互斥量后释放资源。

1#include <semphr.h>
2 
3SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
4//创建一个递归互斥量,成功返回信号量句柄,失败返回NULL